/*
 * Copyright (c) 2025 Industrial Software Feature Database
 */
package com.comac.ins.isfd.service.impl;

import com.comac.ins.common.core.domain.model.LoginUser;
import com.comac.ins.common.core.exception.base.BaseException;
import com.comac.ins.common.core.utils.StringUtils;
import com.comac.ins.isfd.constant.IsfdMeshModelConstants;
import com.comac.ins.isfd.constant.MongoDataConstant;
import com.comac.ins.isfd.constant.enums.IsfdImportMethodEnum;
import com.comac.ins.isfd.domain.IsfdDatabaseMongoQuery;
import com.comac.ins.isfd.domain.IsfdMeshModelBdfCard;
import com.comac.ins.isfd.domain.IsfdMeshModelBdfParseInfo;
import com.comac.ins.isfd.domain.bo.IsfdDatabaseDataImportRecordBo;
import com.comac.ins.isfd.service.IIsfdDatabaseDataImportRecordService;
import com.comac.ins.isfd.service.IIsfdDatabaseMongoService;
import com.comac.ins.isfd.service.IIsfdMeshModelParseService;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.BDFEntity;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.cards.BaseCard;
import com.comac.ins.isfd.util.bdfparser.parsers.BDFParser;
import com.comac.ins.system.service.ISysDictDataService;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 自然网格模型结构化service业务处理
 *
 * @author lj
 * @date 2025-01-15
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class IsfdMeshModelParseServiceImpl implements IIsfdMeshModelParseService {
    @Autowired
    private IIsfdDatabaseMongoService mongoService;

    @Autowired
    private ISysDictDataService iSysDictDataService;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private MongoClient mongoClient;

    @Autowired
    private IIsfdDatabaseDataImportRecordService importRecordService;

    @Autowired
    @Qualifier("mongoBatchExecutor")
    private ExecutorService mongoBatchExecutor;

    @Override
    public List<Document> queryList(IsfdDatabaseMongoQuery queryCondition) {
        return mongoService.findAll(getMeshModelTableName(), queryCondition);
    }

    @Override
    public List<IsfdMeshModelBdfParseInfo> queryParseInfoList(List<String> ids, Integer status) {
        // 拼接条件
        Criteria criteria = new Criteria();
        if (ids != null && !ids.isEmpty()) {
            criteria.and("_id").in(ids);
        }
        if (status != null) {
            criteria.and("status").is(status);
        }

        Query query = new Query(criteria);

        // 指定数据库和集合名（collection名）
        return mongoTemplate.find(query, IsfdMeshModelBdfParseInfo.class,
            MongoDataConstant.BDF_PARSE_INFO_COLLECTION);
    }


    @Override
    public void parseAndSaveBdf(String id, LoginUser loginUser) {

        // 是否已经解析过
        boolean parsed = checkIfParsed(id);

        if (parsed) {
            return;
        }


        IsfdMeshModelBdfParseInfo parseInfo = new IsfdMeshModelBdfParseInfo();
        try {

            // 找到文件
            Document document = mongoService.findById(getMeshModelTableName(), id);
            if (document == null) {
                throw new BaseException("未找到文件！");
            }
            Path filePath = Paths.get(document.getString("fileUrl"));
            // 确保文件存在
            if (!Files.exists(filePath)) {
                throw new BaseException("未找到文件！");
            }

            // 结构化文件
            BDFEntity bdfEntity = BDFParser.readBDF(filePath);
            // 分成两部分，解析信息存一份、所有的卡片存一个collection
            Map<String, Integer> cardCount = bdfEntity.getBulkDataEntry().getCardCount();
            List<BaseCard> cardList = bdfEntity.getBulkDataEntry().getCardList();
            List<String> includeStatements = bdfEntity.getFileManagementStatement().getIncludeStatements();

            int totalCount = 0;
            for (Integer count : cardCount.values()) {
                totalCount = totalCount + count;
            }
            totalCount = totalCount + includeStatements.size();


            parseInfo.set_id(new ObjectId(id));
            parseInfo.setStatus(2);
            parseInfo.setFileName(filePath.getFileName().toString());
            parseInfo.setFileUrl(filePath.toString());
            parseInfo.setTotalCount(totalCount);
            parseInfo.setCardCount(cardCount);
            parseInfo.setIncludeStatements(includeStatements);
            parseInfo.setCollectionName(id);

            // 保存数据
            saveBdfCards(parseInfo.getCollectionName(), cardList, includeStatements);
            saveBdfParseInfo(Collections.singletonList(parseInfo));


        } catch (Exception e) {
            log.error("BDF结构化失败，message: " + e.getMessage(), e);
            String[] stackLines = ExceptionUtils.getStackTrace(e).split("\n");
            String shortMessage = Arrays.stream(stackLines).limit(8).collect(Collectors.joining("\n"));
            parseInfo = new IsfdMeshModelBdfParseInfo();
            parseInfo.set_id(new ObjectId(id));
            parseInfo.setStatus(3);
            parseInfo.setFailedMessage("BDF结构化失败，message: " + shortMessage);
            saveBdfParseInfo(Collections.singletonList(parseInfo));
        }

        // 生成导入记录
        createImportRecord(parseInfo, loginUser);

    }

    @Override
    public boolean checkIfParsed(String id) {
        MongoDatabase database = mongoClient.getDatabase(MongoDataConstant.BDF_DATABASE);
        MongoCollection<Document> collection = database.getCollection(MongoDataConstant.BDF_PARSE_INFO_COLLECTION);

        // 转换 String id -> ObjectId
        ObjectId objectId = new ObjectId(id);

        Document query = new Document("_id", objectId)
            .append("status", 2);
        return collection.find(query).limit(1).iterator().hasNext();
    }

    @Override
    public Set<String> checkIfTasksExist(List<String> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptySet();
        }

        // 将 String 转为 ObjectId
        List<ObjectId> objectIds = ids.stream()
            .filter(id -> id != null && id.length() == 24)
            .map(ObjectId::new)
            .collect(Collectors.toList());

        MongoCollection<Document> collection = mongoClient
            .getDatabase(MongoDataConstant.BDF_DATABASE)
            .getCollection(MongoDataConstant.BDF_PARSE_INFO_COLLECTION);

        // 查询 _id in (...) 的文档
        Document query = new Document("_id", new Document("$in", objectIds))
            .append("status", new Document("$in", Arrays.asList(1, 2)));
        FindIterable<Document> results = collection.find(query).projection(new Document("_id", 1));

        // 提取存在的 id（转为 String）
        Set<String> existingIdSet = new HashSet<>();
        for (Document doc : results) {
            ObjectId objectId = doc.getObjectId("_id");
            existingIdSet.add(objectId.toHexString());
        }

        return existingIdSet;
    }

    @Override
    public void saveBdfParseInfo(List<IsfdMeshModelBdfParseInfo> infoList) {
        if (infoList == null || infoList.isEmpty()) {
            return;
        }

        MongoDatabase database = mongoClient.getDatabase(MongoDataConstant.BDF_DATABASE);
        MongoCollection<Document> collection = database.getCollection(MongoDataConstant.BDF_PARSE_INFO_COLLECTION);

        for (IsfdMeshModelBdfParseInfo info : infoList) {
            if (info.get_id() == null) {
                info.set_id(new ObjectId());  // 自动生成ID
            }

            Document doc = new Document();
            mongoTemplate.getConverter().write(info, doc);
            doc.remove("_class");

            collection.replaceOne(
                Filters.eq("_id", info.get_id()),
                doc,
                new ReplaceOptions().upsert(true)
            );
        }
    }




    private void saveBdfCards(String collectionName, List<BaseCard> cardList, List<String> includeStatements){

        // 创建集合
        createCollection(collectionName);

        // 生成卡片数据
        List<IsfdMeshModelBdfCard> cardsToSave = new ArrayList<>();

        if (!CollectionUtils.isEmpty(cardList)) {
            for (BaseCard card : cardList) {
                IsfdMeshModelBdfCard cardToSave = new IsfdMeshModelBdfCard();
                cardToSave.setName(card.getName());
                cardToSave.setCardId(card.getId());
                cardToSave.setFields(card.getFields());
                cardToSave.setComments(card.getComments());
                cardsToSave.add(cardToSave);
            }
        }

        if (!CollectionUtils.isEmpty(includeStatements)) {
            for (String statement : includeStatements) {
                IsfdMeshModelBdfCard cardToSave = new IsfdMeshModelBdfCard();
                cardToSave.setName("INCLUDE");
                cardToSave.setFileName(statement);
                cardsToSave.add(cardToSave);
            }
        }

        if (CollectionUtils.isEmpty(cardsToSave)) {
            deleteCollection(collectionName);
            throw new BaseException("没有解析出保存信息！");
        }

        // 保存文件
        saveBatch(collectionName, cardsToSave);
    }

    private void saveBatch(String collectionName, List<IsfdMeshModelBdfCard> dataList) {
        if (CollectionUtils.isEmpty(dataList)) {
            log.error("数据批量插入失败，documents为空！");
            throw new BaseException("数据批量插入失败，documents为空！");
        }
        MongoDatabase database = mongoClient.getDatabase(MongoDataConstant.BDF_DATABASE);
        if (!collectionExists(database, collectionName)) {
            log.error("表未创建！: " + collectionName);
            throw new BaseException("表未创建！: " + collectionName);
        }

        int BATCH_SIZE = 400_000; // 更稳定的批量大小
        int totalSize = dataList.size();

        Semaphore semaphore = new Semaphore(8); // 限制并发批次数
        List<CompletableFuture<Void>> futures = new ArrayList<>();

        // 计时
        StopWatch stopWatch = StopWatch.createStarted();
        log.info("开始插入 BDF 数据，集合名：{}，总数：{}", collectionName, dataList.size());

        try {

            for (int i = 0; i < totalSize; i += BATCH_SIZE) {
                List<IsfdMeshModelBdfCard> batch = dataList.subList(i, Math.min(i + BATCH_SIZE, totalSize));

                semaphore.acquire();
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    try {
                        insertBatch(database, collectionName, batch);
                    } catch (Exception e) {
                        log.error("数据插入失败: {}", e.getMessage());
                    } finally {
                        semaphore.release(); // 释放令牌
                    }
                }, mongoBatchExecutor);

                futures.add(future);
            }

            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        } catch (Exception e) {
            log.error("数据批量插入失败, message: " + e.getMessage());
            throw new BaseException("数据插入失败, message: " + e.getMessage());
        }

        // 检查数量
        long count = database.getCollection(collectionName).countDocuments();
        if (count != totalSize) {
            throw new BaseException("数据未全部插入mongo！total: " + totalSize + ", inserted: " + count);
        }

        // 计时
        stopWatch.stop();
        log.info("Task completed in {} milliseconds.", stopWatch.getTime());
    }

    private BulkWriteResult insertBatch(MongoDatabase database, String collectionName, List<IsfdMeshModelBdfCard> batch) {
        List<WriteModel<Document>> bulkOperations = new ArrayList<>();
        for (IsfdMeshModelBdfCard record : batch) {
            Document document = new Document();
            mongoTemplate.getConverter().write(record, document);
            document.remove("_class");
            bulkOperations.add(new InsertOneModel<>(document));
        }

        BulkWriteResult result = database.getCollection(collectionName)
            .bulkWrite(bulkOperations, new BulkWriteOptions().ordered(false)); // 无序批量写入

        bulkOperations.clear(); // 清空列表，减少内存占用
        return result;
    }

    private void createCollection(String collectionName) {
        // 选择数据库（如果不存在，会在插入数据时自动创建）
        MongoDatabase database = mongoClient.getDatabase(MongoDataConstant.BDF_DATABASE);
        // 判断集合是否已经存在
        if (collectionExists(database, collectionName)) {
            // 清空已有数据（删除整个集合）
            MongoCollection<Document> existingCollection = database.getCollection(collectionName);
            existingCollection.drop(); // 删除集合
            log.info("已删除集合: " + collectionName);
        }
        // 创建集合
        try {
            database.createCollection(collectionName);
        } catch (Exception e) {
            log.error("MongoDB中创建表失败，" + e);
            throw new BaseException("MongoDB中创建表失败，" + e);
        }
    }

    private void deleteCollection(String collectionName) {
        // 选择数据库（如果不存在，会在插入数据时自动创建）
        MongoDatabase database = mongoClient.getDatabase(MongoDataConstant.BDF_DATABASE);
        // 判断集合是否已经存在
        if (collectionExists(database, collectionName)) {
            try {
                database.getCollection(collectionName).drop();
                log.info("已删除集合: {}", collectionName);
            } catch (Exception e) {
                log.error("删除集合失败: {}", collectionName, e);
                throw new BaseException("删除MongoDB集合失败: " + collectionName, e.getMessage());
            }
        }
    }

    private boolean collectionExists(MongoDatabase database, String collectionName) {
        for (String name : database.listCollectionNames()) {
            if (name.equals(collectionName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取表名称
     */
    private String getMeshModelTableName() {
        String tableName = iSysDictDataService.selectDictValueByTypeAndLabel(IsfdMeshModelConstants.MODEL_MESH, IsfdMeshModelConstants.MESH_MODEL_TABLE_NAME);
        return StringUtils.isNotBlank(tableName) ? tableName : IsfdMeshModelConstants.DEFAULT_MESH_MODEL_TABLE_NAME;
    }

    @Override
    public Long countBDFCards() {
        MongoDatabase database = mongoClient.getDatabase(MongoDataConstant.BDF_DATABASE);
        long total = 0L;

        for (String collectionName : database.listCollectionNames()) {
            if (!"bdf_parse_info".equals(collectionName)) {
                long count = database.getCollection(collectionName).countDocuments();
                total += count;
            }
        }

        return total;
    }

    @Override
    public void executeParse(List<String> queryIds, LoginUser loginUser) {

        IsfdDatabaseMongoQuery queryCondition = new IsfdDatabaseMongoQuery();

        if (CollectionUtils.isEmpty(queryIds)) {
            log.error("自然网格模型id列表不能为空！");
            throw new BaseException("自然网格模型id列表不能为空！");
        }

        IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition condition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
        condition.setFieldName("_id");
        condition.setOperator("in");
        condition.setValue(String.join(",", queryIds));

        queryCondition.setConditions(new ArrayList<>());
        queryCondition.getConditions().add(condition);


        // 查询数据
        List<Document> uploads = this.queryList(queryCondition);
        if (CollectionUtils.isEmpty(uploads)) {
            log.info("没有BDF文件，任务调度完成");
        }

        List<String> ids = uploads.stream()
            .map(document -> {
                Object idObj = document.get("_id");
                if (idObj instanceof ObjectId) {
                    return ((ObjectId) idObj).toHexString();
                }
                return idObj.toString();
            })
            .toList();
        Set<String> existingIdSet = this.checkIfTasksExist(ids);
        List<Document> newTasks = uploads.stream()
            .filter(document -> {
                Object idObj = document.get("_id");
                String idStr = idObj instanceof ObjectId
                    ? ((ObjectId) idObj).toHexString()
                    : idObj.toString();
                return !existingIdSet.contains(idStr);
            })
            .toList();

        if (CollectionUtils.isEmpty(newTasks)) {
            log.info("没有未结构化的BDF文件，任务调度完成");
            return;
        }

        // 保存解析任务信息
        List<IsfdMeshModelBdfParseInfo> parseInfoList = new ArrayList<>();
        for (Document document : newTasks) {
            String id = document.get("_id").toString();
            IsfdMeshModelBdfParseInfo parseInfo = new IsfdMeshModelBdfParseInfo();
            parseInfo.set_id(new ObjectId(id));
            parseInfo.setStatus(1);
            parseInfoList.add(parseInfo);
        }
        this.saveBdfParseInfo(parseInfoList);

        //一个线程一个任务
        for (Document document : newTasks) {
            final String taskId = document.get("_id").toString(); // 每次循环单独变量
            log.info("BDF结构化开始，id: " + taskId);
            this.parseAndSaveBdf(taskId, loginUser);
        }
    }

    @Override
    public void createImportRecord(IsfdMeshModelBdfParseInfo parseInfo, LoginUser loginUser) {
        String id = parseInfo.get_id().toString();
        Integer status = parseInfo.getStatus();

        if (StringUtils.isBlank(id)) {
            log.error("自然网格模型文件id不能为空！");
            return;
        }

        if (status != 2) {
            log.error("没有成功结构化, id: " + id);
            return;
        }


        String meshModelTableName = getMeshModelTableName();

        IsfdDatabaseDataImportRecordBo importRecordBo = new IsfdDatabaseDataImportRecordBo();
        importRecordBo.setDataFileUrl(parseInfo.getFileUrl());
        Map<String, Integer> tableDataCount = new HashMap<>();
        tableDataCount.put(meshModelTableName, parseInfo.getTotalCount());
        try {
            importRecordService.createImportRecord(importRecordBo, IsfdImportMethodEnum.PARSED, meshModelTableName, tableDataCount, loginUser);
        } catch (Exception e) {
            log.error("导入记录生成失败, id : {}", id, e);
        }
    }
}
